import datetime
import json
import logging
import os
import socket
import sys
import threading
import time
from ctypes import POINTER, cast

import colorlog
import pygame
import requests
from comtypes import CLSCTX_ALL
from flask import Flask, jsonify, request
from flask_cors import CORS
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
from PyQt5 import QtGui
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import *
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.edge.options import Options
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait


PRIORITY = False

def set_volume(volume_level):
    devices = AudioUtilities.GetSpeakers()
    interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
    volume = cast(interface, POINTER(IAudioEndpointVolume))
    volume.SetMasterVolumeLevelScalar(volume_level, None)  # 0 <= volume <= 1.0

color_formatter = colorlog.ColoredFormatter(
        '%(log_color)s%(levelname)s: %(message)s',
        log_colors={
            'DEBUG': 'cyan',
            'INFO': 'green',
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'red,bg_white',
        }
    )
logger = logging.getLogger("logger")

# 服务发现相关配置
BROADCAST_PORT = 37020  # 自定义广播端口
SERVER_PORT = 5000      # 实际服务端口

driver = None

app = Flask(__name__)
CORS(app)  # 允许跨域请求

# 线程安全的歌曲队列和播放控制
song_queue = []
queue_lock = threading.Lock()
playback_event = threading.Event()
stop_flag = False

manual_play = False
manual_event = threading.Event()

# 新增持久化相关配置
DATA_FILE = "song_queue.json"
CYCLE_DATA_FILE = "cycle_play.json"  # 用于保存循环播放的当前 p 值

USERSCRIPT = """
(function() {
    'use strict';
    const div_class_name = 'bili-mini-mask'
    let video_tag_names =["bwp-video","video"]
    console.log(`video_tag_name是${video_tag_names}`)
    function get_video_element(video_tag_names) {
        for (const video_tag_name of video_tag_names){
            const videoElement = document.querySelector(video_tag_name)
            if (videoElement){
                return videoElement
            }
        }
        return null
    }
    const observer = new MutationObserver((mutationsList, observer) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                for (const node of mutation.addedNodes) {
                    if (node instanceof HTMLElement && node.classList.contains(div_class_name)) {
                        node.remove();
                        console.log("删除成功")
                        const videoElement = get_video_element(video_tag_names)
                        console.log("开启播放")
                        if (videoElement.paused){ videoElement.play() }
                    }
                }
            }
        }
    });

    function pause_callback(){
        const videoElement = get_video_element(video_tag_names)
        videoElement.play()
    }
    const config = { childList: true, subtree: false };
    observer.observe(document.body, config);

})();
    """

HEADER = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0",
          "Cookie": "buvid_fp_plain=undefined; enable_web_push=DISABLE; header_theme_version=CLOSE; buvid4=30FB306C-115F-8D37-3C7C-BCF9BC59692018244-023042219-nAkRnw%2F1TLXtHWAdTMX6Fw%3D%3D; fingerprint=198dcb03acfd6819a21aeedaa712c5e1; buvid_fp=34fa803d676183c21b2fac0419855ff8; _uuid=C2E82642-1062E-47D7-10885-12B9104DAD2BB05718infoc; hit-dyn-v2=1; buvid3=7343F0B7-9894-BD42-4234-A3ED318C737727905infoc; b_nut=1738819527; enable_feed_channel=ENABLE; CURRENT_QUALITY=0; rpdid=0zbfAHJo7G|X94Kr044|1ZO|3w1TThfM; DedeUserID=1026569734; DedeUserID__ckMd5=581384da37692f36; LIVE_BUVID=AUTO2617437013590459; PVID=1; bsource=search_bing; home_feed_column=5; browser_resolution=1674-994; b_lsid=64D5510BA_19686D4DA45; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDYyNzgwMjIsImlhdCI6MTc0NjAxODc2MiwicGx0IjotMX0.lYWFPScTg3bIeq8xxy_Sk_U6kvCkKFeXI5HEUg6Xd84; bili_ticket_expires=1746277962; SESSDATA=ef3f82cd%2C1761570823%2C98ce7%2A41CjDKo64iQPDz14Z-ZgeQI6qH3t_ojZIJMv8Q17VJKoxKosr8jKVjS6w9K75SA1FYUTQSVjJINWtQT3g1QVZPdk1YVTlRRGUxRTQwdzVIR2lVa1ZFOFk5ekhuV0dCWlJPcmRrZlU5U18wVGx2MjVFckNQY1VnaXZqaktMQnJsMnUtWkdhcnVpZ2pRIIEC; bili_jct=4583a8ef44840a8fa2c663b3d6f1c2eb; bp_t_offset_1026569734=1061631837291413504; sid=6cc90nmc; CURRENT_FNVAL=2000"
}

def get_page(url: str):
    from urllib.parse import parse_qsl, urlparse
    parsed_url = urlparse(url)
    params = dict(parse_qsl(parsed_url.query))
    p = 1
    if params.get("p"):
        p = int(params['p'])
    return p


def get_song_title(song_url: str):
    p = get_page(song_url)
    lst = song_url.split("?")[0].split("/")
    while not lst[-1]:
        lst.pop()
    bvid = lst[-1]
    url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}'
    try:
        response = requests.get(url, headers=HEADER).json()['data']
        if response['videos'] == 1:
            return response['title']
        return response['pages'][p - 1]['part']
    except Exception as e:
        return ''

def isSame(url1: str, url2: str):
    if url1.split('?')[0] != url2.split('?')[0]:
        return False
    return get_page(url1) == get_page(url2)

def save_queue():
    logger.debug(f"save1 {time.time()}")
    logger.debug(f"lock: {queue_lock.locked()}")
    with queue_lock:
        logger.debug(f"save1.5 {time.time()}")
        f = open(DATA_FILE, 'w')
        logger.debug(f"save1.51 {time.time()}")
        json.dump(song_queue, f, indent=2)
        logger.debug(f"save1.52 {time.time()}")
        f.close()
    logger.debug(f"save2 {time.time()}")

def load_queue():
    global song_queue
    if os.path.exists(DATA_FILE):
        try:
            with open(DATA_FILE, 'r') as f:
                song_queue = json.load(f)
            for song in song_queue:
                if 'title' not in song and song['type'] == 'url':
                    song['title'] = get_song_title(song['url'])

            print(f"已加载 {len(song_queue)} 首保存的歌曲")
        except Exception as e:
            print(f"加载歌曲列表失败: {str(e)}")

def save_cycle_play(p):
    with open(CYCLE_DATA_FILE, 'w') as f:
        json.dump({'p': p}, f)

def load_cycle_play():
    if os.path.exists(CYCLE_DATA_FILE):
        try:
            with open(CYCLE_DATA_FILE, 'r') as f:
                data = json.load(f)
                return data.get('p', 1)
        except Exception as e:
            print(f"加载循环播放信息失败: {str(e)}")
    return 1

@app.route('/add_song_from_client', methods=['POST'])
def add_song():
    data = request.json
    with queue_lock:
        data['title'] = get_song_title(data['url'])
        flag = False
        for song in song_queue:
            flag = flag or isSame(song['url'], data['url'])
        if flag:
            return jsonify({'status': 'failed'})
        song_queue.append(data)
    save_queue()
    return jsonify({'status': 'success'})

@app.route('/search', methods=['GET'])
def search():
    query = request.args.get('q', '')
    # 此处应接入真实搜索API，示例返回模拟数据
    return jsonify([{
        'title': f'{query} - 示例歌曲',
        'artist': '示例歌手',
        'url': 'https://example.com/sample.mp3',
        'type': 'web'
    }])

def time_in_range():
    now = datetime.datetime.now()
    if now.weekday() >= 5 and logger.level != logging.DEBUG:  # 5=Saturday,6=Sunday
        return False
    start = datetime.time(13, 20)
    end = datetime.time(13, 26)
    if logger.level == logging.DEBUG:
        start = datetime.time(10, 00)
        end = datetime.time(10, 2)
    return start <= now.time() <= end


def close_auto(auto_play_switch):
    time.sleep(5)
    should_play = manual_play or time_in_range()
    if should_play:
        try:
            driver.execute_script("arguments[0].click();", auto_play_switch)
            print("已关闭自动连播")
        except:
            pass


# 修改播放逻辑（在播放完成后删除并保存）
def play_song(song):
    global stop_flag, driver
    if song['type'] == 'local':
        # 本地播放逻辑保持不变
        pygame.mixer.init()
        try:
            pygame.mixer.music.load(song['path'])
            pygame.mixer.music.play()
            while pygame.mixer.music.get_busy() and not stop_flag:
                time.sleep(0.5)
        except:
            pass
    elif song['type'] in ('web', 'url'):
        logger.debug(f"start =========== {time.time()}")
        # 配置浏览器选项
        chrome_options = Options()
        # chrome_options.add_argument("--headless")
        chrome_options.add_argument("--unmute-audio")  # 显式取消静音
        chrome_options.add_argument("--autoplay-policy=no-user-gesture-required")  # 允许自动播放
        chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])


        # 初始化浏览器驱动
        logger.debug(f"init {time.time()}")
        driver = webdriver.Edge(options=chrome_options)
        logger.debug(f"after init {time.time()}")

        try:
            # 打开目标网页
            driver.get(song['url'])  # 替换为实际测试地址
            logger.debug(f"after driver.get {time.time()}")
            start_time = time.time()
            # 等待页面上的某个元素加载完成
            element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CLASS_NAME, 'switch-btn'))
            )
            
            last_sound_time = time.time()  # 初始化最后一次检测到声音的时间
            silence_threshold = 5          # 静默阈值（秒）

            driver.minimize_window()

            try:
                logger.debug(f"start close auto {time.time()}")
                # 修改 play_song 函数中的关闭自动播放部分
                try:
                    auto_play_switch = WebDriverWait(driver, 2).until(
                        EC.presence_of_element_located((By.CLASS_NAME, "switch-btn"))
                    )
                    # 检查当前 class 是否包含 'on'
                    current_class = auto_play_switch.get_attribute('class')
                    if 'on' in current_class:
                        # 使用 JavaScript 模拟点击
                        threading.Thread(target=lambda: close_auto(auto_play_switch)).start()
                        # driver.execute_script("arguments[0].click();", auto_play_switch)
                    else:
                        print("自动连播已关闭，无需操作")
                except Exception as e:
                    print("未找到自动连播开关或操作失败:", e)

                logger.debug(f"end close auto {time.time()}")
            except Exception as e:
                print("未找到自动连播开关或操作失败:")

            while time.time() - start_time <= (3 if time_in_range() else 10) * 60 and (time_in_range() or manual_play):
                # 执行JavaScript检测媒体状态
                is_playing = driver.execute_script("""
                    const medias = document.querySelectorAll('video, audio');
                    return Array.from(medias).some(media => {
                        return !media.paused && media.volume > 0 && !media.muted;
                    });
                """)
                try:
                    driver.execute_script(USERSCRIPT)
                except Exception as e:
                    pass
                
                if is_playing and (time_in_range() or manual_play):
                    last_sound_time = time.time()  # 重置计时器
                else:
                    if not (time_in_range() or manual_play):
                        break
                    current_silence = time.time() - last_sound_time
                    print(f"静默持续时间：{current_silence:.1f}秒")
                    
                    if current_silence >= silence_threshold:
                        print(f"连续{silence_threshold}秒静默，关闭浏览器")
                        break
                
                time.sleep(1)  # 检测间隔

        except Exception as e:
            logger.warning(f"运行出错：{str(e)}")
            pass

        finally:
            logger.debug(f"4, {time.time()}")
            logger.debug(f"driver:  {bool(driver)}")
            if driver:
                logger.debug(f"before driver:  {bool(driver)}")
                threading.Thread(target=driver.quit).start()
                logger.debug(f"aft driver:  {bool(driver)}")
            driver = None
            logger.debug(f"5, {time.time()}")


@app.route('/playback_control', methods=['POST'])
def playback_control():
    global manual_play, stop_flag
    command = request.json.get('command')
    
    if command == 'start':
        start_play()
        return jsonify({'status': '播放已启动'})
    elif command == 'stop':
        stop_play()
        return jsonify({'status': '播放已停止'})
    return jsonify({'status': '无效指令'})

def start_play():
    global manual_play, stop_flag

    manual_play = True
    manual_event.set()
    stop_flag = False

def stop_play():
    global manual_play, stop_flag, driver
    manual_play = False
    stop_flag = True
    logger.debug(f"before manual_event {time.time()}")
    manual_event.clear()
    logger.debug(f"after manual_event {time.time()}")
    try:
        pygame.mixer.music.stop()
    except:
        pass

def playback_controller():
    global stop_flag, PRIORITY
    cycle_p = load_cycle_play()
    while True:
        # 修改判断条件，加入手动播放控制
        should_play = manual_play or time_in_range()
        logger.debug(f"while {should_play}")
        
        if should_play and not playback_event.is_set():
            playback_event.set()
            set_volume(0.2)
            print("开始播放...")
            while (manual_play or time_in_range()) and not stop_flag:
                logger.debug(f"before queue_lock {time.time()}")
                next_song = ''
                flag = 0
                with queue_lock:
                    logger.debug(f"in queue_log {time.time()}")
                    if song_queue and not PRIORITY:
                        current_song = song_queue[0]
                        next_song = current_song
                        flag = 1
                    else:
                        cycle_url = f"https://www.bilibili.com/video/BV1qhwye7EVY?p={cycle_p}"
                        cycle_song = {'type': 'url', 'url': cycle_url, 'title': get_song_title(cycle_url)}
                        next_song = cycle_song

                play_song(next_song)
                if flag:
                    song_queue.pop(0)
                    logger.debug(f"after play_song {time.time()}")
                else:
                    cycle_p += 1
                    if cycle_p > 163:
                        cycle_p = 1
                    save_cycle_play(cycle_p)
                # start_time = time.time()
                logger.debug(f"after play_song {time.time()}")
            save_queue()
            print("SAVED!!!!!!!")
            playback_event.clear()
            stop_flag = False
            pygame.mixer.quit()
        time.sleep(5)  # 缩短检查间隔到5秒以提高响应速度

@app.route('/get_queue', methods=['GET'])
def get_queue():
    with queue_lock:
        # 过滤敏感信息并格式化输出
        safe_queue = []
        for song in song_queue:
            item = {
                'title': song.get('title', '未知歌曲'),
                'artist': song.get('artist', '未知艺术家'),
                'type': song['type'],
                'duration': song.get('duration', 0)
            }
            if song['type'] == 'local':
                item['path'] = os.path.basename(song.get('path', ''))
            else:
                item['url'] = song.get('url', '')
            safe_queue.append(item)
    return jsonify(safe_queue)

def broadcast_service():
    """周期性广播服务信息"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    
    message = f"MUSIC_SERVER:{SERVER_PORT}".encode('utf-8')
    
    while True:
        try:
            # 发送到有限广播地址
            sock.sendto(message, ('255.255.255.255', BROADCAST_PORT))
            # 也可以使用子网定向广播地址，如：'192.168.1.255'
            time.sleep(5)  # 每5秒广播一次
        except Exception as e:
            print(f"广播错误: {str(e)}")
            break

@app.route('/')
def index():
    return "Music Server"


class MusicWindow(QWidget):
    def __init__(self):
        super().__init__()
        
        # 窗口置顶设置
        self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
        
        self.initUI()

        self.song_list_model = QtGui.QStandardItemModel()
        self.song_list_show.setModel(self.song_list_model)
        self.refresh_playlist()

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_play_status)
        self.timer.start(1000)  # 每秒更新一次
        
    def initUI(self):
        # 主布局
        main_layout = QHBoxLayout()
        
        # 左边区域
        left_panel = QFrame()
        left_layout = QVBoxLayout()
        
        # 文字区域（使用QTextEdit支持多行输入）
        self.text_area = QLabel()
        self.text_area.setText('<h1>等待播放状态更新...</h1>')
        left_layout.addWidget(self.text_area)
        
        # 按钮区域
        btn_layout = QVBoxLayout()
        self.priority = QPushButton("0")
        self.close_btn = QPushButton("关闭")
        self.close_keep_btn = QPushButton("关闭并保留当前歌")
        self.start_btn = QPushButton("START")
        btn_layout.addWidget(self.priority)
        btn_layout.addWidget(self.close_btn)
        btn_layout.addWidget(self.close_keep_btn)
        btn_layout.addWidget(self.start_btn)
        left_layout.addLayout(btn_layout)
        
        left_panel.setLayout(left_layout)
        
        # 右边区域
        right_panel = QFrame()
        right_layout = QVBoxLayout()
        right_top_layout = QHBoxLayout()
        
        # 刷新按钮
        self.refresh_btn = QPushButton("刷新歌单")
        right_top_layout.addWidget(self.refresh_btn)
        
        # delete btn
        self.del_btn = QPushButton("Delete")
        right_top_layout.addWidget(self.del_btn)

        right_layout.addLayout(right_top_layout)
        
        # 歌单列表
        self.song_list_show= QListView()
        right_layout.addWidget(self.song_list_show)
        
        right_panel.setLayout(right_layout)
        
        # 设置左右区域比例
        main_layout.addWidget(left_panel, 2)  # 左边占3份
        main_layout.addWidget(right_panel, 3)  # 右边占2份
        
        self.setLayout(main_layout)
        
        # 设置窗口属性
        self.setWindowTitle('音乐管理')
        self.setGeometry(300, 300, 800, 500)
        
        # 连接信号
        self.close_btn.clicked.connect(stop_play)
        self.close_keep_btn.clicked.connect(self.close_keep)
        self.refresh_btn.clicked.connect(self.refresh_playlist)
        self.start_btn.clicked.connect(start_play)
        self.del_btn.clicked.connect(self.del_song)
        self.priority.clicked.connect(self.upd_pri)

    def upd_pri(self):
        global PRIORITY

        self.priority.setText("0" if PRIORITY else "1")
        PRIORITY = not PRIORITY

    def close_keep(self):
        latest_song = None
        if song_queue:
            latest_song = song_queue[0]
        stop_play() 
        if latest_song:
            song_queue.insert(0, latest_song)

    def refresh_playlist(self):
        """刷新歌单示例方法"""
        self.song_list_model.clear()
        for song in song_queue:
            item = QtGui.QStandardItem(song['title'])
            self.song_list_model.appendRow(item)

    def update_play_status(self):
        global manual_play
        should_play = manual_play or time_in_range()
        status_text = "正在播放" if should_play else "已停止"
        self.text_area.setText(f'<h1>{status_text}</h1>')

    def del_song(self):
        try:
            selected_index = self.song_list_show.currentIndex()
            selected_content = self.song_list_model.itemFromIndex(selected_index)
            for i, song in enumerate(song_queue):
                if song['title'] == selected_content.text():
                    del song_queue[i]
            self.song_list_model.removeRow(selected_content.row())
            save_queue()
        except Exception as e:
            return


def show_panel():
    apppanel = QApplication(sys.argv)
    ex = MusicWindow()
    ex.show()
    sys.exit(apppanel.exec_())

def start_service():
    app.run(host='0.0.0.0', port=SERVER_PORT)
        
if __name__ == '__main__':
    logger.setLevel(logging.DEBUG)
    output_handal = logging.StreamHandler()
    output_handal.setFormatter(color_formatter)
    logger.addHandler(output_handal)
    # 启动广播线程
    broadcast_thread = threading.Thread(target=broadcast_service, daemon=True)
    broadcast_thread.start()

    load_queue()
    pygame.init()
    threading.Thread(target=playback_controller, daemon=True).start()
    threading.Thread(target=start_service, daemon=True).start()
    show_panel()